package fr.ul.ade.secunde.database;

import java.util.List;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import fr.ul.ade.secunde.database.AdeWebContract.LessonColumns;
import fr.ul.ade.secunde.database.AdeWebDatabase.InsertQueries;
import fr.ul.ade.secunde.database.AdeWebDatabase.Tables;
import fr.ul.ade.secunde.database.AdeWebDatabase.Views;

/**
 * Classe responsable des accès (lectures/écritures) au données de notre
 * application.
 * 
 * @author Michaël Schnell
 * @since Jul 5, 2013
 * 
 * @version 1.5
 */
public class AdeWebProvider
		extends ContentProvider {

	private static final UriMatcher sUriMatcher = buildUriMatcher();

	private static final int SCHEDULE = 100;
	private static final int SCHEDULE_ID = 101;

	private static final int LESSON_ID = 200;
	private static final int LESSON_SCHEDULE = 201;
	private static final int LESSON_SCHEDULE_BETWEEN = 202;

	private static final int LESSON_DISPLAY = 300;
	private static final int LESSON_DISPLAY_ID = 301;
	private static final int LESSON_DISPLAY_SCHEDULE_BETWEEN = 302;

	private static final int PROJECT = 400;

	private static final int STUDENT_NUMBER = 500;

	private static final int SCHEDULE_ERROR = 600;
	private static final int SCHEDULE_CREATE_ERROR = 601;

	/**
	 * Construit le parseur pour les URIs identifiant les données de notre
	 * application.
	 * 
	 * @return parseur construit
	 */
	private static UriMatcher buildUriMatcher() {
		final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
		final String authority = AdeWebContract.CONTENT_AUTHORITY;

		matcher.addURI(authority, AdeWebContract.PATH_SCHEDULE, SCHEDULE);
		matcher.addURI(authority,
				AdeWebContract.PATH_SCHEDULE + "/#", SCHEDULE_ID); //$NON-NLS-1$

		String lessonPath = AdeWebContract.PATH_SCHEDULE
				+ "/#/" + AdeWebContract.PATH_LESSON; //$NON-NLS-1$
		matcher.addURI(authority, lessonPath, LESSON_SCHEDULE);
		matcher.addURI(authority, lessonPath + "/#", LESSON_ID); //$NON-NLS-1$
		matcher.addURI(authority,
				lessonPath + "/" + AdeWebContract.PATH_BETWEEN + "/*/*", LESSON_SCHEDULE_BETWEEN); //$NON-NLS-1$ //$NON-NLS-2$

		String displayPath = AdeWebContract.PATH_SCHEDULE
				+ "/#/" + AdeWebContract.PATH_DISPLAY; //$NON-NLS-1$
		matcher.addURI(authority, displayPath, LESSON_DISPLAY);
		matcher.addURI(authority, displayPath + "/#", LESSON_DISPLAY_ID); //$NON-NLS-1$
		matcher.addURI(authority,
				displayPath + "/" + AdeWebContract.PATH_BETWEEN + "/*/*", LESSON_SCHEDULE_BETWEEN); //$NON-NLS-1$ //$NON-NLS-2$

		matcher.addURI(authority, AdeWebContract.PATH_PROJECT, PROJECT);

		matcher.addURI(authority,
				AdeWebContract.PATH_STUDENT_NUMBER,
				STUDENT_NUMBER);

		matcher.addURI(authority, AdeWebContract.PATH_ERROR + "/" //$NON-NLS-1$
				+ AdeWebContract.PATH_SCHEDULE + "/#", SCHEDULE_ERROR); //$NON-NLS-1$
		matcher.addURI(authority, AdeWebContract.PATH_ERROR + "/" //$NON-NLS-1$
				+ AdeWebContract.PATH_SCHEDULE + "/" //$NON-NLS-1$
				+ AdeWebContract.PATH_CREATE, SCHEDULE_CREATE_ERROR);

		return matcher;
	}

	private AdeWebDatabase mOpenHelper;

	@Override
	public boolean onCreate() {
		this.mOpenHelper = new AdeWebDatabase(this.getContext());
		return true;
	}

	@Override
	public String getType(Uri uri) {
		final int match = sUriMatcher.match(uri);
		switch (match) {
		case SCHEDULE:
			return AdeWebContract.Schedule.CONTENT_TYPE;
		case SCHEDULE_ID:
			return AdeWebContract.Schedule.CONTENT_ITEM_TYPE;
		case LESSON_ID:
			return AdeWebContract.Lesson.CONTENT_ITEM_TYPE;
		case LESSON_SCHEDULE:
		case LESSON_SCHEDULE_BETWEEN:
		case LESSON_DISPLAY_SCHEDULE_BETWEEN:
			return AdeWebContract.Lesson.CONTENT_TYPE;
		case PROJECT:
			return AdeWebContract.Project.CONTENT_TYPE;
		case STUDENT_NUMBER:
			return AdeWebContract.StudentNumber.CONTENT_TYPE;
		case SCHEDULE_ERROR:
		case SCHEDULE_CREATE_ERROR:
			return AdeWebContract.Error.CONTENT_TYPE;
		default:
			throw new UnsupportedOperationException("Unknown uri: " + uri); //$NON-NLS-1$
		}
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
		final SQLiteDatabase db = this.mOpenHelper.getReadableDatabase();
		final SelectionBuilder builder = buildSimpleSelection(uri);
		return builder.query(db, projection, sortOrder);
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		final SQLiteDatabase db = this.mOpenHelper.getWritableDatabase();
		final int match = sUriMatcher.match(uri);

		Uri returnUri = null;
		switch (match) {
		case SCHEDULE: {
			// FIXME Validiation des valeurs
			long id = db.insertOrThrow(Views.SCHEDULE, null, values);
			returnUri = ContentUris.withAppendedId(uri, id);
			break;
		}
		case LESSON_SCHEDULE: {
			long id = db.insertOrThrow(Views.LESSON, null, values);
			returnUri = ContentUris.withAppendedId(uri, id);
			break;
		}
		case LESSON_DISPLAY: {
			long id = db.insertOrThrow(Views.LESSON_DISPLAY, null, values);
			returnUri = ContentUris.withAppendedId(uri, id);
			break;
		}
		case PROJECT: {
			long id = db.insertOrThrow(Tables.PROJECT, null, values);
			returnUri = ContentUris.withAppendedId(uri, id);
			break;
		}
		case STUDENT_NUMBER: {
			long id = db.insertOrThrow(Tables.STUDENT_NUMBER, null, values);
			returnUri = ContentUris.withAppendedId(uri, id);
			break;
		}
		case SCHEDULE_CREATE_ERROR:
		case SCHEDULE_ERROR: {
			long id = db.insertOrThrow(Tables.ERROR, null, values);
			returnUri = ContentUris.withAppendedId(uri, id);
			break;
		}
		default: {
			throw new UnsupportedOperationException("Unknown uri: " + uri); //$NON-NLS-1$
		}
		}// switch
			// FIXME Check notified URI
		getContext().getContentResolver().notifyChange(returnUri, null, false);
		return returnUri;
	}

	@Override
	public int bulkInsert(Uri uri, ContentValues[] values) {
		// FIXME Add notified URI
		final int match = sUriMatcher.match(uri);
		int nInserted = 0;
		switch (match) {
		case LESSON_SCHEDULE: {
			final SQLiteDatabase db = this.mOpenHelper.getWritableDatabase();
			final SQLiteStatement insertStmt = db
					.compileStatement(InsertQueries.INSERT_LESSON_STMT);
			for (ContentValues lessonValues : values) {
				bindLessonStmt(insertStmt, lessonValues);
				// TODO executeInsert retourne une erreur (-1)
				// pour une raison indéterminée alors que l'insertion a été
				// effectuée
				insertStmt.executeInsert();
				nInserted++;
			}// for
			insertStmt.close();
			if (nInserted > 0) {
				long scheduleId = values[0]
						.getAsLong(LessonColumns.LESSON_SCHEDULE)
						.longValue();
				this.getContext()
						.getContentResolver()
						.notifyChange(AdeWebContract.Lesson.buildLessonUri(scheduleId),
								null,
								false);
			}
			break;
		}// case LESSON_SCHEDULE
		case LESSON_DISPLAY: {
			final SQLiteDatabase db = this.mOpenHelper.getWritableDatabase();
			final SQLiteStatement insertStmt = db
					.compileStatement(InsertQueries.INSERT_LESSON_DISPLAY_STMT);
			for (ContentValues lessonValues : values) {
				bindLessonDisplayStmt(insertStmt, lessonValues);
				// TODO executeInsert retourne une erreur (-1)
				// pour une raison indéterminée alors que l'insertion a été
				// effectuée
				insertStmt.executeInsert();
				nInserted++;
			}// for
			insertStmt.close();
			if (nInserted > 0) {
				long scheduleId = values[0]
						.getAsLong(LessonColumns.LESSON_SCHEDULE)
						.longValue();
				this.getContext()
						.getContentResolver()
						.notifyChange(AdeWebContract.Lesson.buildDisplayUri(scheduleId),
								null,
								false);
			}
			break;
		}// case LESSON_DISPLAY
		default: {
			nInserted = super.bulkInsert(uri, values);
		}// default
		}// switch
		return nInserted;
	}// bulkInsert(Uri, ContentValues[])

	private static void bindLessonStmt(SQLiteStatement insertStmt,
			ContentValues lessonValues) {
		insertStmt.bindString(InsertQueries.LESSON_TITLE_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_TITLE));
		insertStmt.bindString(InsertQueries.LESSON_DATE_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_DATE));
		insertStmt.bindString(InsertQueries.LESSON_START_TIME_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_START_TIME));
		insertStmt.bindString(InsertQueries.LESSON_END_TIME_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_END_TIME));
		insertStmt.bindString(InsertQueries.LESSON_SCHEDULE_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_SCHEDULE));
		insertStmt.bindString(InsertQueries.LESSON_CHANGE_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_CHANGE));
		insertStmt.bindString(InsertQueries.LESSON_CREATED_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_CREATED));
		insertStmt.bindString(InsertQueries.LESSON_ROOMS_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_ROOMS));
		insertStmt.bindString(InsertQueries.LESSON_GROUPS_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_GROUPS));
		insertStmt.bindString(InsertQueries.LESSON_TEACHERS_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_TEACHERS));
		insertStmt.bindString(InsertQueries.LESSON_COLOR_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_COLOR));
		insertStmt.bindString(InsertQueries.LESSON_NOTE_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_NOTE));
		insertStmt.bindString(InsertQueries.LESSON_GUID_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_GUID));
		insertStmt.bindString(InsertQueries.LESSON_DELETED_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_DELETED));
		insertStmt.bindString(InsertQueries.LESSON_LAST_UPDATE_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_LAST_UPDATE));
	}

	private static void bindLessonDisplayStmt(SQLiteStatement insertStmt,
			ContentValues lessonValues) {
		bindStringOrNull(insertStmt,
				InsertQueries.LESSON_DISPLAY_SCHEDULE_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_SCHEDULE));
		bindStringOrNull(insertStmt,
				InsertQueries.LESSON_DISPLAY_ID_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_ID));
		insertStmt.bindString(InsertQueries.LESSON_DISPLAY_ORDER_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_ORDER));
		bindStringOrNull(insertStmt,
				InsertQueries.LESSON_DISPLAY_DATE_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_DATE));
		bindStringOrNull(insertStmt,
				InsertQueries.LESSON_DISPLAY_END_TIME_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_END_TIME));
		insertStmt.bindString(InsertQueries.LESSON_DISPLAY_TYPE_INDEX,
				lessonValues.getAsString(LessonColumns.LESSON_TYPE));
	}

	/**
	 * @param insertStmt
	 * @param index
	 * @param value
	 */
	private static void bindStringOrNull(SQLiteStatement insertStmt, int index,
			String value) {
		if (value == null) {
			insertStmt.bindNull(index);
		} else {
			insertStmt.bindString(index, value);
		}
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
		final SQLiteDatabase db = this.mOpenHelper.getWritableDatabase();
		final SelectionBuilder builder = buildSimpleUpdateSelection(uri);
		int retVal = builder.where(selection, selectionArgs).update(db, values);
		// FIXME Check notified URI
		getContext().getContentResolver().notifyChange(uri, null, false);
		return retVal;
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		final SQLiteDatabase db = this.mOpenHelper.getWritableDatabase();
		final SelectionBuilder builder = buildSimpleSelection(uri);
		int retVal = builder.where(selection, selectionArgs).delete(db);
		// FIXME Check notified URI
		getContext().getContentResolver().notifyChange(uri, null, false);
		return retVal;
	}

	/**
	 * Build a simple {@link SelectionBuilder} to match the requested
	 * {@link Uri}. This is usually enough to support {@link #insert},
	 * {@link #update}, and {@link #delete} operations.
	 */
	private static SelectionBuilder buildSimpleSelection(Uri uri) {
		final SelectionBuilder builder = new SelectionBuilder();
		final int match = sUriMatcher.match(uri);
		switch (match) {
		case SCHEDULE: {
			return builder.table(Views.SCHEDULE);
		}
		case SCHEDULE_ID: {
			final String scheduleId = Long.toString(ContentUris.parseId(uri));
			return builder.table(Views.SCHEDULE).where(Views.ID_QUERY,
					scheduleId);
		}
		case LESSON_DISPLAY_ID:
		case LESSON_ID: {
			final String lessonId = Long.toString(ContentUris.parseId(uri));
			return builder.table(Views.LESSON_DISPLAY).where(Views.ID_QUERY,
					lessonId);
		}
		case LESSON_DISPLAY: {
			final List<String> segments = uri.getPathSegments();
			final String scheduleId = Uri.decode(segments.get(1));
			return builder.table(Views.LESSON_DISPLAY)
					.where(Views.LESSON_SCHEDULE_QUERY, scheduleId);
		}
		case LESSON_SCHEDULE: {
			final List<String> segments = uri.getPathSegments();
			final String scheduleId = Uri.decode(segments.get(1));
			return builder.table(Views.LESSON)
					.where(Views.LESSON_SCHEDULE_QUERY, scheduleId);
		}
		case LESSON_SCHEDULE_BETWEEN: {
			final List<String> segments = uri.getPathSegments();
			final String scheduleId = Uri.decode(segments.get(1));
			final String startDate = Uri.decode(segments.get(4));
			final String endDate = Uri.decode(segments.get(5));
			return builder
					.table(Views.LESSON)
					.where(Views.LESSON_SCHEDULE_QUERY, scheduleId)
					.where(String.format(Views.LESSON_SCHEDULE_BETWEEN_QUERY,
							LessonColumns.LESSON_DATE,
							LessonColumns.LESSON_END_TIME,
							startDate,
							endDate));
		}
		case LESSON_DISPLAY_SCHEDULE_BETWEEN: {
			final List<String> segments = uri.getPathSegments();
			final String scheduleId = Uri.decode(segments.get(1));
			final String startDate = Uri.decode(segments.get(4));
			final String endDate = Uri.decode(segments.get(5));
			return builder
					.table(Views.LESSON_DISPLAY)
					.where(Views.LESSON_SCHEDULE_QUERY, scheduleId)
					.where(String.format(Views.LESSON_SCHEDULE_BETWEEN_QUERY,
							LessonColumns.LESSON_DATE,
							LessonColumns.LESSON_END_TIME,
							startDate,
							endDate));
		}
		case PROJECT: {
			return builder.table(Tables.PROJECT);
		}
		case STUDENT_NUMBER: {
			return builder.table(Tables.STUDENT_NUMBER);
		}
		case SCHEDULE_CREATE_ERROR: {
			return builder.table(Tables.ERROR);
		}
		case SCHEDULE_ERROR: {
			final String scheduleId = Long.toString(ContentUris.parseId(uri));
			return builder.table(Tables.STUDENT_NUMBER).where(Views.ID_QUERY,
					scheduleId);
		}
		default: {
			throw new UnsupportedOperationException("Unknown uri: " + uri); //$NON-NLS-1$
		}
		}
	}

	/**
	 * Build a simple {@link SelectionBuilder} to match the requested
	 * {@link Uri} for {@link #update} operations.
	 */
	private static SelectionBuilder buildSimpleUpdateSelection(Uri uri) {
		final SelectionBuilder builder = new SelectionBuilder();
		final int match = sUriMatcher.match(uri);
		switch (match) {
		case LESSON_SCHEDULE: {
			final List<String> segments = uri.getPathSegments();
			final String scheduleId = Uri.decode(segments.get(1));
			return builder.table(Views.LESSON)
					.where(Views.LESSON_SCHEDULE_QUERY, scheduleId);
		}
		case SCHEDULE_ID: {
			// FIXME Test schedule values
		}
		//$FALL-THROUGH$
		default: {
			return buildSimpleSelection(uri);
		}
		}
	}

}// class AdeWebProvider